﻿using PetkaWebDav.Server.Framework.Enums;
using PetkaWebDav.Server.Framework.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Xml.XPath;

namespace PetkaWebDav.Server.Framework.BaseClasses
{
    /// <summary>
    /// Dav Method Framework Base Class
    /// </summary>
    public abstract class DavMethodBase : IDavRequest
    {

        private HttpApplication __httpApplication;
        private int __abortStatusCode = (int)ServerResponseCode.Ok;
        private int __httpResponseCode = (int)ServerResponseCode.Ok;
        private int __internalStatusCode = (int)ServerResponseCode.Ok;
        private string __responseXml = "";
        private bool __fireOnProcessDavRequest = true;
        private XPathNavigator __requestXmlNavigator;

        /// <summary>
        /// Method HttpApplication
        /// </summary>
        public HttpApplication HttpApplication
        {
            get
            {
                return this.__httpApplication;
            }
            set
            {
                this.__httpApplication = value;

                //Microsoft Required header - Thanks to Michael Liebman for this tidbit!
                this.__httpApplication.Response.AddHeader("MS-Author-Via", "DAV");
            }
        }

        /// <summary>
        /// WebDav Requested Depth
        /// <seealso cref="DepthType"/>
        /// </summary>
        protected DepthType RequestDepth
        {
            get
            {
                //Based on the spec the default is infinity if a ["Depth"] header is not included
                DepthType _depth = DepthType.Infinity;

                if (HttpApplication == null)
                    throw new ArgumentNullException("HttpApplication", InternalFunctions.GetResourceString("ArgumentNullException", "HttpApplication"));

                try
                {
                    _depth = (DepthType)Enum.Parse(typeof(DepthType), HttpApplication.Request.Headers["Depth"], true);
                }
                catch (ArgumentException) { }
                return _depth;
            }
        }

        /// <summary>
        /// HttpRequest Xml
        /// </summary>
        /// <remarks>
        ///		This will return null if there is no RequestXml
        ///	</remarks>
        protected XPathNavigator RequestXml
        {
            get
            {
                if (HttpApplication == null)
                    throw new ArgumentNullException("HttpApplication", InternalFunctions.GetResourceString("ArgumentNullException", "HttpApplication"));

                if (this.__requestXmlNavigator == null && HttpApplication.Request.InputStream.Length > 0)
                {
                    //XDocument _document = XDocument.Load(new StreamReader(HttpApplication.Request.InputStream));
                    //var _other = _document.CreateNavigator();

                    XPathDocument _requestXPathDoc = new XPathDocument(HttpApplication.Request.InputStream);
                    this.__requestXmlNavigator = _requestXPathDoc.CreateNavigator();
                }

                return this.__requestXmlNavigator;
            }
        }


        #region Delegate Section
        /// <summary>
        /// Dav Request handler delegate
        /// </summary>
        protected delegate void DavRequestEventHandler(object sender, EventArgs e);

        /// <summary>
        /// Occurs when the server is about to process the Dav request
        /// </summary>
        protected event DavRequestEventHandler PreProcessDavRequest;

        /// <summary>
        /// Raises the PreprocessDavRequest event
        /// </summary>
        protected void OnPreProcessDavRequest(EventArgs e)
        {
            if (PreProcessDavRequest != null)
                PreProcessDavRequest(this, e);
        }

        /// <summary>
        /// Occurs when the server has completed processing the Dav request prior to 
        /// sending the ResponseXml.
        /// <seealso cref="ResponseXml"/>
        /// </summary>
        protected event DavRequestEventHandler PostProcessDavRequest;

        /// <summary>
        /// Raises the PostProcessDavRequest event
        /// </summary>
        protected void OnPostProcessDavRequest(EventArgs e)
        {
            if (PostProcessDavRequest != null)
                PostProcessDavRequest(this, e);
        }

        /// <summary>
        /// Dav Request process handler delegate
        /// </summary>
        protected delegate void DavProcessEventHandler(object sender, EventArgs e);

        /// <summary>
        /// Occurs when the server int has completed processing the Dav request
        /// </summary>
        protected event DavProcessEventHandler ProcessDavRequest;

        /// <summary>
        /// Raises the ProcessDavRequest event
        /// </summary>
        protected void OnProcessDavRequest(EventArgs e)
        {
            if (ProcessDavRequest != null)
                ProcessDavRequest(this, e);
        }

        /// <summary>
        /// Dav internal Request process handler delegate
        /// </summary>
        internal delegate int DavInternalProcessHandler(object sender, EventArgs e);

        /// <summary>
        /// Internal event... occurs when the server int has completed processing the Dav request
        /// </summary>
        internal event DavInternalProcessHandler InternalProcessDavRequest;

        /// <summary>
        /// Raises the InternalProcessDavRequest event
        /// </summary>
        internal int OnInternalProcessDavRequest(EventArgs e)
        {
            int _internalResponseCode = (int)ServerResponseCode.Ok;
            if (InternalProcessDavRequest != null)
                _internalResponseCode = InternalProcessDavRequest(this, e);

            return _internalResponseCode;
        }

        /// <summary>
        /// Dav internal Request validator delegate
        /// </summary>
        internal delegate int DavRequestValidator(object sender, EventArgs e);

        /// <summary>
        /// Occurs when the server has is validating the Dav request
        /// </summary>
        /// <remarks>Fires after the PreprocessDavRequest</remarks>
        internal event DavRequestValidator ValidateDavRequest;

        /// <summary>
        /// Raises the ValidateDavRequest event
        /// </summary>
        internal int OnValidateDavRequest(EventArgs e)
        {
            int _internalValidationCode = (int)ServerResponseCode.Ok;

            if (ValidateDavRequest != null)
                _internalValidationCode = ValidateDavRequest(this, e);

            return _internalValidationCode;
        }
        #endregion

        #region IDavRequest Members
        /// <summary>
        /// Keep the interface implementation hidden
        /// </summary>
        /// <returns></returns>
        int IDavRequest.ProcessRequest()
        {
            if (WebDavProcessor.DebugFilePath != null)
            {
                int _inputStreamLength = (int)HttpApplication.Request.InputStream.Length;
                if (_inputStreamLength > 0)
                {
                    byte[] _requestInput = HttpApplication.Request.BinaryRead(_inputStreamLength);

                    InternalFunctions.WriteDebugLog
                    (
                        "RequestXml:" + Environment.NewLine + System.Text.Encoding.ASCII.GetString(_requestInput)
                    );

                    HttpApplication.Request.InputStream.Position = 0;
                }
                else
                    InternalFunctions.WriteDebugLog("RequestXml: [null]");
            }

            //Reset all the variables
            this.SetResponseXml("");
            this.__internalStatusCode = (int)ServerResponseCode.Ok;
            this.__abortStatusCode = (int)ServerResponseCode.Ok;
            this.__httpResponseCode = (int)ServerResponseCode.Ok;
            this.FireOnProcessDavRequest = true;

            //Notify the client the server is about to process the dav request
            this.OnPreProcessDavRequest(EventArgs.Empty);

            if (IsRequestAborted())
                InternalFunctions.WriteDebugLog("WebDAV request aborted... abort status code: " + this.__abortStatusCode);

            else
            {
                bool _continueWithRequest = false;

                if (WebDavProcessor.ModuleAuthentication == Security.Authentication.Forms)
                {
                    if (HttpApplication.Context.Request.IsAuthenticated == false)
                    {
                        string _httpMethod = HttpApplication.Request.HttpMethod;
                        if (_httpMethod.Equals("Options", StringComparison.CurrentCultureIgnoreCase))
                        {
                            HttpApplication.Response.Headers.Add("X-FORMS_BASED_AUTH_REQUIRED", "http://localhost:46961/OnSuccess/Login?ReturnUrl=http://localhost:46961/OnSuccess");
                            HttpApplication.Response.Headers.Add("X-FORMS_BASED_AUTH_RETURN_URL", "http://localhost:46961/OnSuccess");
                            HttpApplication.Response.Headers.Add("X-FORMS_BASED_AUTH_DIALOG_SIZE", "800x600");
                            __httpResponseCode = (int)ServerResponseCode.Forbidden;
                        }
                        else
                        {
                            __httpResponseCode = (int)ServerResponseCode.Unauthorized;
                            //string userAgent = HttpContext.Current.Request.UserAgent;
                            //if (userAgent == null || userAgent.IndexOf("Mozilla", StringComparison.InvariantCultureIgnoreCase) < 0)
                            //{
                            //    __httpResponseCode = (int)ServerResponseCode.NotFound;
                            //}
                            //else
                            //{
                            //    __httpResponseCode = (int)ServerResponseCode.Unauthorized;
                            //}
                        }
                    }
                    else
                    {
                        _continueWithRequest = true;
                    }
                }

                if (WebDavProcessor.ModuleAuthentication == Security.Authentication.None || _continueWithRequest == true)
                {
                    //Validate the Dav Request... this will always return ServerResponseCode.OK if successful
                    this.__httpResponseCode = this.OnValidateDavRequest(EventArgs.Empty);

                    InternalFunctions.WriteDebugLog("Validating WebDAV request: " + this.__httpResponseCode);
                    if (this.__httpResponseCode == (int)ServerResponseCode.Ok)
                    {
                        //Fire the request event
                        if (FireOnProcessDavRequest)
                            this.OnProcessDavRequest(EventArgs.Empty);

                        if (!IsRequestAborted())
                            this.__httpResponseCode = this.OnInternalProcessDavRequest(EventArgs.Empty);
                    }
                    this.OnPostProcessDavRequest(EventArgs.Empty);

                    if (!IsRequestAborted())
                    {
                        //Set the base response
                        if (ResponseXml.Length != 0)
                        {
                            HttpApplication.Response.ContentEncoding = System.Text.Encoding.UTF8;
                            HttpApplication.Response.ContentType = "text/xml";
                            HttpApplication.Response.Write(ResponseXml);

                            //For debugging
                            InternalFunctions.WriteDebugLog("ResponseXml:" + Environment.NewLine + ResponseXml);
                        }
                        else
                        {
                            string dd = HttpApplication.Response.ContentType;
                            InternalFunctions.WriteDebugLog("ResponseXml: [null]");
                        }
                    }
                }

            }

            return this.HttpResponseCode;
        }
        #endregion

        #region Internal Methods
        /// <summary>
        /// Enable base classes to selectively disable firing of the OnProcessDavRequest
        /// <seealso cref="DavLockBase"/>
        /// </summary>
        internal bool FireOnProcessDavRequest
        {
            get
            {
                return this.__fireOnProcessDavRequest;
            }
            set
            {
                this.__fireOnProcessDavRequest = value;
            }
        }

        /// <summary>
        /// Reads the stream object and sets ResponseXml 
        /// </summary>
        /// <param name="stream"></param>
        internal void SetResponseXml(Stream stream)
        {
            using (StreamReader _streamReader = new StreamReader(stream, Encoding.UTF8))
            {
                //Go to the begining of the stream
                _streamReader.BaseStream.Position = 0;
                this.SetResponseXml(_streamReader.ReadToEnd());
            }
        }

        /// <summary>
        /// Set the ResponseXml
        /// </summary>
        /// <param name="responseXml"></param>
        internal void SetResponseXml(string responseXml)
        {
            this.__responseXml = responseXml;
        }
        #endregion

        /// <summary>
        /// HttpRequest Length
        /// </summary>
        protected long RequestLength
        {
            get
            {
                if (HttpApplication == null)
                    throw new ArgumentNullException("HttpApplication", InternalFunctions.GetResourceString("ArgumentNullException", "HttpApplication"));

                return HttpApplication.Request.InputStream.Length;
            }
        }

        /// <summary>
        /// Returns the URL segment without the root information
        /// </summary>
        protected string RelativeRequestPath
        {
            get
            {
                if (HttpApplication == null)
                    throw new ArgumentNullException("HttpApplication", InternalFunctions.GetResourceString("ArgumentNullException", "HttpApplication"));

                InternalFunctions.WriteDebugLog("DavMethodBase - RelativeRequestPath: " + this.HttpApplication.Request.Path);

                //return InternalFunctions.GetRelativePath(this.HttpApplication, this.HttpApplication.Request.FilePath);
                return InternalFunctions.GetFileName(this.HttpApplication, this.HttpApplication.Request.Path);

            }
        }


        /// <summary>
        /// Returns the current HttpResponseCode
        /// </summary>
        protected int HttpResponseCode
        {
            get
            {
                int _responseCode;

                if (IsRequestAborted())
                    _responseCode = this.__abortStatusCode;
                else if (this.__internalStatusCode != (int)ServerResponseCode.Ok)
                    _responseCode = this.__internalStatusCode;
                else
                    _responseCode = this.__httpResponseCode;

                return _responseCode;
            }
        }

        /// <summary>
        /// The value written to the HttpResponse response after the OnPostProcessDavRequest event has fired
        /// <seealso cref="OnPostProcessDavRequest"/>
        /// </summary>
        protected string ResponseXml
        {
            get
            {
                return this.__responseXml;
            }
        }

        /// <summary>
        /// Aborts the current request processing
        /// </summary>
        protected void AbortRequest(System.Enum responseCode)
        {
            if (responseCode == null)
                throw new ArgumentNullException("HttpApplication", InternalFunctions.GetResourceString("ArgumentNullException", "ResponseCode"));

            Type _enumType = responseCode.GetType();
            if (responseCode.GetTypeCode() == TypeCode.Int32)
                AbortRequest((int)System.Enum.Parse(_enumType, responseCode.ToString(), true));
            else
                throw new WebDavException(InternalFunctions.GetResourceString("InvalidEnumIntType"));
        }

        /// <summary>
        /// Aborts the current request processing
        /// </summary>
        /// <param name="responseCode">HttpResponse status code</param>
        protected internal void AbortRequest(int responseCode)
        {
            if (responseCode >= 200 && responseCode < 300)
                throw new WebDavException(InternalFunctions.GetResourceString("InvalidResponseCode"));

            this.__abortStatusCode = responseCode;
        }

        /// <summary>
        /// Check to see if the request has been aborted
        /// </summary>
        /// <returns></returns>
        private bool IsRequestAborted()
        {
            return (this.__abortStatusCode != (int)ServerResponseCode.Ok);
        }
    }
}
